7 Regresión en series de tiempo - Algoritmo Facebook’s Prophet
7.1 Introducción
En este capítulo se combinan modelos causales con datos de series de tiempo mediante algoritmos de aprendizaje estadístico. Esto supone pasar de ver la serie únicamente como una sucesión de observaciones dependientes en el tiempo, a interpretarla también como el resultado de una relación de regresión entre la variable respuesta y uno o varios regresores.
En este capítulo se realizan las siguientes actividades para interpretar la serie de potencia de una subestación eléctrica:
- Limpieza, imputación y exploración de la serie.
- Ajuste de modelos autorregresivos clásicos (ARIMA, ETS).
- Ajuste del modelo Prophet, utilizando la variable temperatura como regresor.
- Comparación de desempeño entre modelos (ARIMA, ETS, Prophet).
7.2 Regresión y series de tiempo
7.2.1 Regresión con errores ARMA/ARIMA
En la regresión clásica se supone que los errores son independientes y con varianza constante. En las series de tiempo esta suposición suele violarse: los errores muestran autocorrelación, tendencia o cambios de varianza. Una manera de incorporar esta estructura es suponer que el error sigue un proceso ARMA/ARIMA.
Un modelo general puede escribirse como:
\[ y_t = \beta_0 + \beta_1 x_{1,t} + \cdots + \beta_k x_{k,t} + \eta_t, \]
donde \(\eta_t\) sigue un proceso ARIMA. Esto es lo que se conoce como un modelo de regresión con errores ARIMA, o un modelo ARIMAX cuando los regresores son variables externas.
- Todas las variables del modelo deben ser estacionarias o, al menos, cointegradas.
- Cuando no lo son, se trabaja sobre las diferencias (modelo en diferencias) en lugar de los niveles.
7.2.2 Estacionariedad y modelos en diferencias
Si las series \(y_t\) y \(x_{i,t}\) no son estacionarias, una práctica habitual es diferenciarlas:
\[ y_t' = y_t - y_{t-1}, \quad x_{i,t}' = x_{i,t} - x_{i,t-1}. \]
Así, podemos ajustar un modelo de la forma:
\[ y_t' = \beta_1 x'_{1,t} + \cdots + \beta_k x'_{k,t} + \eta_t', \]
donde \(\eta_t'\) sigue un proceso ARMA. Tal como se discute en el material de la unidad, esto es equivalente a un modelo de regresión con errores ARIMA, pero expresado en diferencias. De este modo, la regresión respeta la estructura de dependencia temporal de la serie.
7.2.3 Prophet como regresión no lineal en el tiempo
El modelo Prophet, introducido por Facebook (Taylor & Letham, 2018), puede entenderse como una regresión no lineal sobre el tiempo:
\[ y_t = g(t) + s(t) + h(t) + \varepsilon_t, \]
donde:
- \(g(t)\) representa la tendencia (lineal por tramos o logística).
- \(s(t)\) captura los comportamientos estacionales mediante términos de Fourier.
- \(h(t)\) incorpora los efectos de eventos especiales (por ejemplo, festivos).
- \(\varepsilon_t\) es un término de error aproximadamente de ruido blanco.
En este modelo, el tiempo actúa como un regresor principal, al que se agregan regresores derivados (tendencia por tramos, términos de Fourier para estacionalidad, variables indicadoras para eventos). Por esto, Prophet encaja muy bien dentro de la idea de regresión en series de tiempo, pero con componentes flexibles y no lineales.
7.2.4 Justificación para la serie de potencia eléctrica
La serie de potencia de una subestación eléctrica presenta típicamente:
- Comportamientos cíclicos (diarios, semanales…).
- Cambios de nivel asociados a patrones de uso, clima, calendario, etc.
- Variabilidad considerable, con posibles picos y valles.
Desde esta perspectiva, es razonable interpretar la serie como el resultado de:
- Una tendencia \(g(t)\) asociada a la evolución de la demanda en el tiempo.
- Estacionalidades \(s(t)\) debidas a ciclos de consumo (por ejemplo, horario laboral vs. nocturno).
- Posibles efectos \(h(t)\) asociados a fechas específicas (mantenimientos, eventos especiales).
- Un componente aleatorio \(\varepsilon_t\).
Por lo tanto, tiene sentido aplicar Prophet a esta serie y, al mismo tiempo, compararlo con modelos ARIMA y ETS utilizados previamente.
7.3 Descripción de los datos
Los datos corresponden a mediciones de potencia (kW) registradas en una subestación eléctrica, a la temperatura (°C) y al índice temporal fecha (fecha-hora).
csv_dir <- "C:/Users/Lenovo/PUJ Cali/OSCAR VELASQUEZ CHALA - Proyecto Aplicado - Proy. Demanda Electrica/2. Fuentes de Datos"
csv_name <- "2025.11.15.potencia_temperatura.csv"
ruta_archivo <- file.path(csv_dir, csv_name)
datos_raw <- read.csv(ruta_archivo,
header = TRUE,
sep = ",",
stringsAsFactors = FALSE)
str(datos_raw)## 'data.frame': 58448 obs. of 3 variables:
## $ fecha : chr "2019-01-01 00:00:00" "2019-01-01 01:00:00" "2019-01-01 02:00:00" "2019-01-01 03:00:00" ...
## $ temp : num 21.9 20.5 19.4 18.2 18.1 20.7 20.5 20.7 21.8 27.7 ...
## $ potencia: num 778 766 765 735 731 ...
## fecha temp potencia
## 1 2019-01-01 00:00:00 21.9 778.2125
## 2 2019-01-01 01:00:00 20.5 766.2975
## 3 2019-01-01 02:00:00 19.4 764.6450
## 4 2019-01-01 03:00:00 18.2 734.6750
## 5 2019-01-01 04:00:00 18.1 731.0275
## 6 2019-01-01 05:00:00 20.7 735.7500
- Los datos corresponden a una serie horaria entre los años 2019–2025.
7.4 Limpieza e imputación de faltantes
A continuación, se aplica la limpieza e imputación de datos faltanters.
datos <- datos_raw %>%
janitor::clean_names() %>%
mutate(
fecha = lubridate::ymd_hms(fecha)
) %>%
arrange(fecha)
# Comprobar NA en la serie
cat("Cantidad de datos faltantes variable potencia, dataset original: ",
sum(is.na(datos$potencia)), "\n")## Cantidad de datos faltantes variable potencia, dataset original: 456
cat("Cantidad de datos faltantes variable temperatura, dataset original: ",
sum(is.na(datos$temp)), "\n")## Cantidad de datos faltantes variable temperatura, dataset original: 0
# Interpolar NA para no romper la serie horaria
# (usa método de forecast, respeta patrones)
serie_sin_na <- forecast::na.interp(datos$potencia)
# Volvemos a pegar a los datos
datos <- datos %>%
mutate(potencia = serie_sin_na)
# Comprobar NA en la serie
cat("Cantidad de datos faltantes, después de la imputación: ",
sum(is.na(datos$potencia)), "\n")## Cantidad de datos faltantes, después de la imputación: 0
## Resumen Serie de Datos - Potencia Subestación Eléctrica
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0 2593 3194 3308 4068 6676
## Resumen Serie de Datos - Temperatura
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 17.00 22.80 24.50 24.88 26.90 35.10
La variable potencia tenía faltantes, pero todos fueron imputados mediante interpolación (na.interp), preservando la continuidad temporal.
Los datos de la variable potencia presentan una distribución asimétrica hacia la derecha, con valores máximos significativamente superiores al promedio. Valores cercanos a 0 kW (mínimo) representan probablemente caídas abruptas: cortes de energía, mantenimientos, fallos de medición o cargas extremadamente bajas fuera de horario.
La variable temperatura no presenta faltantes, lo cual es muy favorable para la aplicación de modelos con regresores externos (ARIMAX, Prophet).
Los datos de temperatura indican un promedio estable (≈ 25 °C), variabilidad moderada entre 17–35 °C, comportamientos cíclicos diarios (noches frías, días cálidos) y ciclos estacionales (épocas cálidas/frías del año).
7.5 Exploración inicial de la serie
7.5.1 Serie de datos: Potencia y Temperatura
fig <- plot_ly()
# ----------------------------
# SERIE 1: Potencia (eje izquierdo)
# ----------------------------
fig <- fig %>%
add_lines(
data = datos,
x = ~fecha,
y = ~potencia,
name = "Potencia",
line = list(color = "blue")
)
# ----------------------------
# SERIE 2: Temperatura (eje derecho)
# ----------------------------
fig <- fig %>%
add_lines(
data = datos,
x = ~fecha,
y = ~temp,
name = "Temperatura",
yaxis = "y2",
line = list(color = "red")
)
# ----------------------------
# CONFIGURACIÓN DE LOS EJES
# ----------------------------
fig <- fig %>%
layout(
title = "Potencia y Temperatura - Exploración Temporal",
xaxis = list(title = "Fecha"),
yaxis = list(title = "Potencia (kW)", side = "left"),
yaxis2 = list(
title = "Temperatura (°C)",
overlaying = "y",
side = "right",
showgrid = FALSE
),
legend = list(
x = 1.05, # Posición horizontal (derecha)
y = 0.8, # Centrada verticalmente
orientation = "v" # Leyenda vertical
)
)
figfig_pot <- plot_ly(datos, x = ~fecha, y = ~potencia,
type="scatter", mode="lines",
name="Potencia", line=list(color="blue"))
fig_temp <- plot_ly(datos, x = ~fecha, y = ~temp,
type="scatter", mode="lines",
name="Temperatura", line=list(color="red"))
subplot(
fig_pot, fig_temp,
nrows = 2,
shareX = TRUE,
titleX = TRUE,
titleY = TRUE
) %>%
layout(title = "Series de Potencia y Temperatura")- En la gráfica de potencia se observa:
- Picos pronunciados y caídas abruptas.
- A partir de 2022-2025 la serie parece tener un incremento estructural, posiblemente por crecimiento de la demanda, expansión de cargas, cambios climáticos, aumento de producción industrial/comercial.
- Existe un patrón ondulante, correspondiente a series horarias con estacionalidad diaria.
- La potencia eléctrica muestra estacionalidad diaria, tendencia creciente a lo largo de los años, ruido alto + picos pronunciados, caídas esporádicas (valores casi cero).
- En la gráfica de temperatura se observa:
- Curva completamente ondulante, repetitiva, con ciclos bien definidos.
- Se observan noches más frías, días más cálidos.
- La temperatura muestra un patrón regular y estable, ciclos repetitivos diarios, incrementos estacionales ligeros, no tiene valores atípicos extremos.
- Observando las gráficas superpuestas de potencia y temperatura se observa:
- Un patron sincronizado: cuando la temperatura sube, la Potencia sube; y cuando la temperatura baja, la Potencia baja.
- La potencia tiene un desfase mínimo respecto a la temperatura, lo que sugiere relación causal directa (climatización) y sensibilidad inmediata al calor.
- La temperatura máxima entre 11am–3pm coincide con picos de potencia.
- La temperatura mínima en madrugada coincide con mínimos de potencia.
- Correlación directa fuerte entre las variables a nivel diario, asociación creciente a medida que sube la carga base de la subestación, altas temperaturas inducen alta demanda eléctrica (principalmente por climatización). por lo tanto, se asumen que la señal de potencia combina demanda térmica (relación directa con temperatura) y demanda no térmica (ruido de operación, cargas industriales/comerciales).
7.5.2 Líneas suavizadas (LOESS - suavizamiento estimado localmente)
# Gráfico con línea original + suavizado LOESS
fig_loess <- plot_ly()
fig_loess <- fig_loess %>%
add_lines(
data = datos,
x = ~fecha,
y = ~potencia,
name = "Potencia",
line = list(color = "steelblue")
) %>%
add_lines(
data = datos,
x = ~fecha,
y = ~fitted(loess(potencia ~ as.numeric(fecha), span = 0.1)),
name = "LOESS suavizado",
line = list(color = "red", width = 3)
) %>%
layout(
title = "Potencia con suavizado LOESS",
xaxis = list(title = "Fecha"),
yaxis = list(title = "Potencia")
)
fig_loessLa curva de suavizado LOESS representa la tendencia real, libre de ruido, durante 2019–2025, se observa :
- Tendencia general creciente, debido tal vez al incremento sostenido de carga conectada, al aumento progresivo del consumo de hogares/industria o a mayor uso de equipos eléctricos.
- Comportamientos por periodos:
- 2019 – inicio de 2020, asciende rápidamente desde valores bajos (~0–1500 kW) hasta niveles más estables de 2500–2800 kW.
- 2020 – 2021, la demanda se mantiene relativamente estable entre 2700 y 3000 kW.
- 2022 – crecimiento moderado, se observa un incremento más claro hacia 3500–3800 kW.
- 2023 – ascenso fuerte, muestra mayor pendiente hacia niveles superiores a 4000 kW.
- 2024 - pico máximo estructural, el suavizado alcanza niveles cercanos a 4500–4800 kW.
- 2025 – estabilización, la curva parece estabilizarse o incluso reducirse ligeramente.
La demanda eléctrica ha crecido de manera sostenida entre 2019 y
La serie no es estacionaria, tiene tendencia creciente.
La serie original tiene mucho ruido y valores atípicos, pero LOESS revela un patrón claro y robusto.
El comportamiento refleja aumento creciente de carga instalada, mayor uso de equipos eléctricos, influencia significativa de la temperatura, efectos estacionales de larga duración.
LOESS demuestra que se deben usar modelos de forecasting capaces de capturar tendencia, incorporar regresores externos (como temperatura), manejar alta variabilidad, soportar valores atípicos.
7.5.3 Promedios móviles
datos$SMA_24 <- zoo::rollmean(datos$potencia, k = 24, fill = NA) # móvil diario
datos$SMA_168 <- zoo::rollmean(datos$potencia, k = 24*7, fill = NA) # móvil semanal
datos$EMA_24 <- forecast::ma(datos$potencia, order = 24) # suavizado exponencial simple
fig_ma <- plot_ly() %>%
add_lines(data = datos, x = ~fecha, y = ~potencia, name = "Potencia") %>%
add_lines(data = datos, x = ~fecha, y = ~SMA_24, name = "SMA 24h", line=list(color="orange")) %>%
add_lines(data = datos, x = ~fecha, y = ~SMA_168, name = "SMA 7 días", line=list(color="green")) %>%
add_lines(data = datos, x = ~fecha, y = ~EMA_24, name = "EMA 24h", line=list(color="red")) %>%
layout(
title = "Promedios móviles (SMA y EMA)",
xaxis = list(title = "Fecha"),
yaxis = list(title = "Potencia")
)
fig_ma